package com.example.swagger.config;


import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.*;

/**
 * @Author scott
 */
@Slf4j
@Configuration
@EnableSwagger2
@RequiredArgsConstructor
public class Swagger2Config implements WebMvcConfigurer {
    /**
     * 指定要扫描的路径
     */
    private final static String[] BASE_PACKAGES = new String[]{"com.example.swagger"};
    private final ApplicationContext applicationContext;
    /**
     * 请求头token键
     */
    private final static String ACCESS_TOKEN = "Authorization";


    /**
     * 所有api分组信息
     *
     * @return Docket ${@link Docket}
     * @author zxiaozhou
     * @date 2020-07-06 14:52
     */
    @Bean
    public Docket allGroup() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo("all"))
                .groupName("all")
                .select()
                .apis(basePackage(BASE_PACKAGES))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(this.securityContexts());
    }


    /**
     * 其他api分组信息
     *
     * @author zxiaozhou
     * @date 2020-07-06 14:52
     */
    @Bean
    public void otherGroups() {
        Map<String, String> versions = new HashMap<>(4);
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            if (Objects.nonNull(apiOperation)) {
                String value = apiOperation.value();
                if (StringUtils.isNotBlank(apiOperation.value())) {
                    String[] versionInfos = getVersions(value);
                    if (ArrayUtils.getLength(versionInfos) > 0) {
                        for (String versionInfo : versionInfos) {
                            versions.put(versionInfo.replace(".", ""), versionInfo);
                        }
                    }
                }
            }
        }
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        for (Map.Entry<String, String> version : versions.entrySet()) {
            Docket docket = createDocket(version.getValue());
            defaultListableBeanFactory.registerSingleton(version.getKey(), docket);
        }
    }


    /**
     * 重写basePackage方法，使能够实现自定义多包扫描
     *
     * @param basePackages ${@link String[]}
     * @return Predicate<RequestHandler> ${@link Predicate<RequestHandler>}
     * @author zxiaozhou
     * @date 2020-07-06 18:01
     */
    public static Predicate<RequestHandler> basePackage(final String[] basePackages) {
        return input -> declaringClass(input).transform(handlerPackage(basePackages)).or(true);
    }


    private static Function<Class<?>, Boolean> handlerPackage(final String[] basePackages) {
        return input -> {
            for (String basePackage : basePackages) {
                boolean isMatch = input.getPackage().getName().startsWith(basePackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }


    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }


    private List<ApiKey> securitySchemes() {
        List<ApiKey> list = new ArrayList<>();
        list.add(new ApiKey(ACCESS_TOKEN, ACCESS_TOKEN, "header"));
        return list;
    }


    private List<SecurityContext> securityContexts() {
        List<SecurityContext> list = new ArrayList<>();
        list.add(SecurityContext.builder()
                .securityReferences(this.defaultAuth())
                .forPaths(PathSelectors.regex("^(?!auth).*$"))
                .build());
        return list;
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> list = new ArrayList<>();
        list.add(new SecurityReference(ACCESS_TOKEN, authorizationScopes));
        return list;
    }


    /**
     * 构建版本信息
     *
     * @param version
     * @return
     */
    protected static Docket createDocket(String version) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(version))
                .groupName(version)
                .select()
                .apis(basePackage(BASE_PACKAGES))
                .apis(input -> matchVersion(input, version))
                .paths(PathSelectors.any())
                .build();
    }


    /**
     * 匹配swagger版本
     *
     * @param input   ${@link String} 注解解析
     * @param version ${@link String} 版本信息
     * @return String[] ${@link String[]}
     * @author zxiaozhou
     * @date 2020-07-06 10:49
     */
    protected static boolean matchVersion(RequestHandler input, String version) {
        System.out.println(version);
        String[] versions = getVersions(input);
        for (String v : versions) {
            if (version.equalsIgnoreCase(v)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 获取版本信息
     *
     * @param input ${@link RequestHandler}
     * @return String[] ${@link String[]}
     * @author zxiaozhou
     * @date 2020-07-06 11:11
     */
    protected static String[] getVersions(RequestHandler input) {
        if (Objects.isNull(input)) {
            return new String[]{};
        }
        Optional<ApiOperation> controllerAnnotation = input.findAnnotation(ApiOperation.class);
        if (controllerAnnotation.isPresent()) {
            ApiOperation apiOperation = controllerAnnotation.get();
            return getVersions(apiOperation.value());

        }
        return new String[]{};
    }


    /**
     * 解析版本信息
     *
     * @param version ${@link String}
     * @return String[] ${@link String[]}
     * @author zxiaozhou
     * @date 2020-07-06 18:02
     */
    private static String[] getVersions(String version) {
        String reg = "(.)*(@[\\(（].*[\\)）])(.)*";
        if (version.matches(reg)) {
            version = version.replaceAll(reg, "$2");
            reg = "(@[\\(（])(.*)([\\)）])";
            version = version.replaceAll(reg, "$2");
            return version.split("[,，]");
        }
        return new String[]{};
    }


    /**
     * api文档的详细信息函数,注意这里的注解引用的是哪个
     *
     * @return
     */
    protected static ApiInfo apiInfo(String version) {
        return new ApiInfoBuilder()
                // //大标题
                .title("系统API接口文档")
                // 版本号
                .version(version)
                // 描述
                .description("系统API接口")
                .build();
    }
}
